Mediator Pattern
3 min readMediator Pattern
TL;DR
Define an object that encapsulates how a set of objects interact. The mediator promotes loose coupling by keeping objects from referring to each other explicitly.
How it works
π§© Example β Mediator coordinating order validation, risk and execution
public interface ITradeMediator
{
void Notify(object sender, string ev, Order order);
}
public class TradeMediator : ITradeMediator
{
private readonly OrderValidator _validator;
private readonly RiskService _risk;
private readonly TradeExecutor _executor;
public TradeMediator(OrderValidator validator, RiskService risk, TradeExecutor executor)
{
_validator = validator;
_risk = risk;
_executor = executor;
}
public void Notify(object sender, string ev, Order order)
{
if (ev == "PlaceOrder")
{
if (!_validator.Validate(order))
{
Console.WriteLine("Validation failed");
return;
}
if (!_risk.Check(order))
{
Console.WriteLine("Risk check failed");
return;
}
_executor.Execute(order);
}
}
}
public class OrderValidator
{
private readonly ITradeMediator _mediator;
public OrderValidator(ITradeMediator mediator) => _mediator = mediator;
public bool Validate(Order order) => order != null && order.Amount > 0;
}
public class RiskService
{
private readonly ITradeMediator _mediator;
public RiskService(ITradeMediator mediator) => _mediator = mediator;
public bool Check(Order order) => order.Amount <= 100000; // simple rule
}
public class TradeExecutor
{
private readonly ITradeMediator _mediator;
public TradeExecutor(ITradeMediator mediator) => _mediator = mediator;
public void Execute(Order order) => Console.WriteLine($"Executed {order.Id} for {order.Amount}");
}
// --- Usage ---
// Compose mediator with concrete components
// var mediator = new TradeMediator(new OrderValidator(null), new RiskService(null), new TradeExecutor(null));
// Wire mediator into components and use mediator.Notify(this, "PlaceOrder", order);
β Why it matters:
- Centralizes interaction logic between components (validation, risk, execution).
- Reduces direct dependencies between components, improving testability and maintainability.
- Makes it easier to change coordination rules without touching each component.
Quick recall Q&A
It avoids spaghetti dependencies among collaborating components (validator, risk, executor) by centralizing communication in a mediator. Components interact via the mediator instead of referencing each other directly.
MediatR routes requests (commands/queries/notifications) to their handlers through a central mediator, letting senders remain unaware of receivers. Pipeline behaviors provide cross-cutting features without tight coupling.
Use MediatR for request/response flows in application layers. Build custom mediators when orchestrating domain services with bespoke protocols or when you need full control over orchestration semantics.
Components depend only on the mediator interface, so tests can supply stub mediators to assert interactions. You can validate coordination logic by testing the mediator in isolation.
The mediator can become a god object if it accumulates too much logic. Mitigate by splitting mediators per feature or layering policies/pipeline behaviors to keep responsibilities focused.
Keep mediators focused on coordination (who to notify next) and delegate business invariants to domain services/entities. If logic belongs to the domain, move it there rather than hiding it in the mediator.
Define async methods (Task NotifyAsync(...)) and await dependent operations. MediatR supports async handlers out of the box, ensuring non-blocking orchestration.
Combine with CQRS (commands/queries flow through the mediator), decorators (pipeline behaviors like logging/validation), and event sourcing (mediator publishes domain events).
Register handlers or collaborators via DI so new behaviors can be added without changing existing code. For example, add a compliance handler to the notification pipeline without modifying producer services.
Instrument pipeline behaviors or interceptors to log request duration, handler outcomes, and errors. This keeps observability centralized and avoids duplicating logging in every handler.